// 版权所有2012 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包格式实现Go源代码的标准格式。
// 
// 请注意，Go源代码的格式会随着时间的推移而变化，因此依赖于
// 一致格式的工具应该执行特定版本的gofmt二进制
// 而不是使用此包。这样，格式就稳定了，而且不需要每次gofmt更改时都重新编译这些工具。
// 
// 例如，直接使用此软件包的预提交检查的行为将根据每个开发人员使用的Go版本而有所不同，导致
// 检查天生脆弱。
package format

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
)

// 保持这些与cmd/gofmt/gofmt同步。去
const (
	tabWidth    = 8
	printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers

	// PrinterNormalizedNumbers是指在打印时规范化数字文字前缀
	// 和指数。请参阅https:
	// 
	// 该值在go/printer中定义，专门用于go/format和cmd/gofmt。
	printerNormalizeNumbers = 1 << 30
)

var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth}

const parserMode = parser.ParseComments

// 节点以规范的gofmt样式格式化节点，并将结果写入dst。
// 
// 节点类型必须为*ast。文件，*打印机。CommentedNode，[]ast。12月，
// /[]ast。Stmt，或与ast兼容的赋值。Expr，ast。十二月，阿斯特。规格，
// 或ast。Stmt。节点不修改节点。导入不会针对代表部分源文件的
// 节点进行排序（例如，如果节点是
// 不是*ast.File或*printer.CommentedNode没有包装*ast.File）。
// 
// 函数可能会提前返回（在写入整个结果之前）
// 并返回格式错误，例如由于AST不正确。
// 
func Node(dst io.Writer, fset *token.FileSet, node any) error {
	// 确定我们是否有完整的源文件（file！=nil）。
	var file *ast.File
	var cnode *printer.CommentedNode
	switch n := node.(type) {
	case *ast.File:
		file = n
	case *printer.CommentedNode:
		if f, ok := n.Node.(*ast.File); ok {
			file = f
			cnode = n
		}
	}

	// 必要时对导入进行排序。
	if file != nil && hasUnsortedImports(file) {
		// 复制AST，因为AST。职业体育运动具有破坏性。
		// TODO（gri）可以更有效地完成这项工作。我们不应该到这里。如果我们这样做，提供良好的诊断。
		var buf bytes.Buffer
		err := config.Fprint(&buf, fset, file)
		if err != nil {
			return err
		}
		file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
		if err != nil {
			return fmt.Errorf("format.Node internal error (%s)", err)
		}
		ast.SortImports(fset, file)

		// 将新文件与已排序的导入一起使用。
		node = file
		if cnode != nil {
			node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
		}
	}

	return config.Fprint(dst, fset, node)
}

// 源以规范的gofmt样式格式化src，并返回结果
// 或（I/O或语法）错误。src应该是语法上正确的Go源文件，或者Go声明或语句列表。
// 
// 如果src是部分源文件，则将src 
// 的前导和尾随空间应用于结果（这样它与src具有相同的前导和尾随
// 空间），并且结果的缩进量与包含代码的src的第一行
// 相同。导入不会对部分源文件进行排序。
// 
func Source(src []byte) ([]byte, error) {
	fset := token.NewFileSet()
	file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
	if err != nil {
		return nil, err
	}

	if sourceAdj == nil {
		// 完整的源文件。ABCFDG 
		ast.SortImports(fset, file)
	}

	return format(fset, file, sourceAdj, indentAdj, src, config)
}

func hasUnsortedImports(file *ast.File) bool {
	for _, d := range file.Decls {
		d, ok := d.(*ast.GenDecl)
		if !ok || d.Tok != token.IMPORT {
			// 不是进口报关单，所以我们结束了。
			// 进口总是第一位的。
			return false
		}
		if d.Lparen.IsValid() {
			// 现在假设所有分组的导入都未排序。
			// TODO（gri）应检查它们是否已排序。
			return true
		}
		// 默认情况下，未分组的导入将被排序。
	}
	return false
}
